package com.samphanie.auiu.library.restful

import com.samphanie.auiu.library.restful.annotation.*
import java.lang.reflect.*
import kotlin.IllegalStateException

/**
 * @author Dries
 **/
class MethodParser(
    private val baseUrl: String,
    method: Method
) {
    private lateinit var relativeUrl: String
    private lateinit var returnType: Type
    private var replaceRelativeUrl: String? = null
    private var domainUrl: String? = null
    private var formPost: Boolean = true
    private var httpMethod: Int = -1
    private var cacheStrategy: Int = CacheStrategy.NET_ONLY
    private var headers: MutableMap<String, String> = mutableMapOf()
    private var parameters: MutableMap<String, String> = mutableMapOf()

    init {
        parseMethodAnnotation(method)
        parseMethodReturnType(method)
        // parseMethodParameters(method, args)
    }

    /**
     * interface ApiService {
     *      @Headers("auth-token: token", "accountId: 123456")
     *      @BaseUrl("https://api.auiucloud.com/api/")
     *      @POST("/cities/{province}")
     *      @GET("/cities/all")
     *      fun listCities(@Path("province") province: Int, @Filed("page") page: Int): AuiuCall<JsonObject>
     * }
     */
    private fun parseMethodAnnotation(method: Method) {
        val annotations = method.annotations
        for (annotation in annotations) {
            when (annotation) {
                is CacheStrategy -> {
                    cacheStrategy = annotation.value
                }
                is Get -> {
                    relativeUrl = annotation.value
                    httpMethod = AuiuRequest.METHOD.GET
                }
                is Post -> {
                    relativeUrl = annotation.value
                    httpMethod = AuiuRequest.METHOD.POST
                    formPost = annotation.formPost
                }
                is Headers -> {
                    val headersArray = annotation.value
                    for (header in headersArray) {
                        val colon = header.indexOf(":")
                        check(!(colon == 0 || colon == 1)) {
                            String.format(
                                "@headers value must be in the form [name:value] ,but found [%s]",
                                header
                            )
                        }

                        val name = header.substring(0, colon)
                        val value = header.substring(colon + 1).trim()
                        headers[name] = value
                    }
                }
                is BaseUrl -> {
                    domainUrl = annotation.value
                }
                else -> {
                    throw IllegalStateException("cannot handle method annotation:" + annotation.javaClass.toString())
                }
            }

        }


        require((httpMethod == AuiuRequest.METHOD.GET) || (httpMethod == AuiuRequest.METHOD.POST)) {
            String.format("method %s must has one if GET,POST", method.name)
        }

        if (domainUrl == null) {
            domainUrl = baseUrl
        }
    }

    private fun parseMethodParameters(method: Method, args: Array<Any>) {
        // 每次调用api接口时  应该吧上一次解析到的参数清理掉，因为methodParser存在复用
        parameters.clear()
        val parameterAnnotations = method.parameterAnnotations
        val equals = parameterAnnotations.size == args.size
        require(equals) {
            String().format(
                "arguments annotations count %s dont match expect count %s",
                parameterAnnotations.size,
                args.size
            )
        }

        //args
        for (index in args.indices) {
            val annotations = parameterAnnotations[index]
            require(annotations.size <= 1) {
                "filed can only has one annotation :index = $index"
            }

            val value = args[index]
            require(isPrimitive(value)) {
                "8 basic types are supported for now, index=$index"
            }
            val annotation = annotations[0]
            if (annotation is Filed) {
                val key = annotation.value
                // val value = args[index]
                parameters[key] = value.toString()
            } else if (annotation is Path) {
                val replaceName = annotation.value
                val replacement = value.toString()
                if (replaceName != null && replacement != null) {
                    val newRelativeUrl = relativeUrl.replace("{$replaceName}", replacement)
                    replaceRelativeUrl = newRelativeUrl
                }
            } else if(annotation is CacheStrategy) {
                cacheStrategy = value as Int
            } else {
                throw IllegalStateException("cannot handle parameter annotation : ${annotation.javaClass}")
            }

        }
    }

    private fun validateGenericType(type: Type): Boolean {
        /**
         *wrong
         *  fun test():HiCall<Any>
         *  fun test():HiCall<List<*>>
         *  fun test():HiCall<ApiInterface>
         *expect
         *  fun test():HiCall<User>
         */
        //如果指定的泛型是集合类型的，那还检查集合的泛型参数
        if (type is GenericArrayType) {
            return validateGenericType(type.genericComponentType)
        }
        //如果指定的泛型是一个接口 也不行
        if (type is TypeVariable<*>) {
            return false
        }
        //如果指定的泛型是一个通配符 ？extends Number 也不行
        if (type is WildcardType) {
            return false
        }

        return true
    }

    private fun isPrimitive(value: Any): Boolean {

        if (value.javaClass == String::class.java) {
            return true
        }

        try {
            // int byte short long boolean char double float
            val field = value.javaClass.getField("TYPE")
            val clazz = field[null] as Class<*>
            if (clazz.isPrimitive) {
                return true
            }
        } catch (e:IllegalAccessException) {
            e.printStackTrace()
        } catch (e:NoSuchFieldException) {
            e.printStackTrace()
        }

        return false
    }

    private fun parseMethodReturnType(method: Method) {
        if (method.returnType != AuiuCall::class.java) {
            throw IllegalStateException(
                String.format(
                    "method %s must be type of AuiuCall.class",
                    method.name
                )
            )
        }
        val genericReturnType = method.genericReturnType
        if (genericReturnType is ParameterizedType) {
            val actualTypeArguments = genericReturnType.actualTypeArguments
            val argument = actualTypeArguments[0]
            require(actualTypeArguments.size == 1) {
                "method %s can only has one generic return type"
            }

            returnType = argument
        } else {
            throw IllegalStateException(
                String.format(
                    "method %s must has one gerneric return type",
                    method.name
                )
            )
        }
    }

    fun newRequest(
        method: Method,
        args: Array<out Any>?
    ): AuiuRequest {
        val arguments: Array<Any> = args as Array<Any>? ?: arrayOf()
        parseMethodParameters(method, arguments)

        val request = AuiuRequest()
        request.domainUrl = domainUrl
        request.formPost = formPost
        request.returnType = returnType
        request.relativeUrl = replaceRelativeUrl ?: relativeUrl
        request.parameters = parameters
        request.headers = headers
        request.httpMethod = httpMethod
        request.cacheStrategy = cacheStrategy

        return request
    }

    companion object {
        fun parse(baseUrl: String, method: Method): MethodParser {
            return MethodParser(baseUrl, method)
        }
    }

}